﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace SystemHelperLibrary.API.USB
{
	public class DismountVolume
	{
		private DismountVolume() { }

		//PInvoke of Win32 API's that are needed
		internal const int INVALID_HANDLE_VALUE = -1;
		internal const int GENERIC_READ = unchecked((int)0x80000000);
		internal const int GENERIC_WRITE = unchecked((int)0x40000000);
		internal const int FILE_SHARE_READ = unchecked((int)0x00000001);
		internal const int FILE_SHARE_WRITE = unchecked((int)0x00000002);
		internal const int OPEN_EXISTING = unchecked((int)3);
		internal const int FSCTL_LOCK_VOLUME = unchecked((int)0x00090018);
		internal const int FSCTL_DISMOUNT_VOLUME = unchecked((int)0x00090020);
		internal const int IOCTL_STORAGE_EJECT_MEDIA = unchecked((int)0x002D4808);
		internal const int IOCTL_STORAGE_MEDIA_REMOVAL = unchecked((int)0x002D4804);

		[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
			IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

		[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
		private static extern bool CloseHandle(IntPtr handle);

		[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
		private static extern bool DeviceIoControl(IntPtr hDevice, int dwIoControlCode, byte[] lpInBuffer,
			 int nInBufferSize, byte[] lpOutBuffer, int nOutBufferSize, out int lpBytesReturned, IntPtr lpOverlapped);

		/// <summary>
		/// Dismount function to call
		/// </summary>
		/// <param name="driveLetter"></param>
		/// <returns></returns>
		public static bool Dismount(string driveLetter)
		{
			try
			{
				//driveLetter may be passed as f: or f:\. In the last case the \ must be removed of the driveletter
				if (driveLetter.Contains("\\"))
				{
					driveLetter = driveLetter.Substring(0, driveLetter.IndexOf("\\"));
				}
				IntPtr handle = DoDisMount(driveLetter);
				if (handle != IntPtr.Zero)
				{
					CloseHandle(handle);
					return true;
				}
				return false;
			}
			catch (Exception)
			{
				return false;
			}
		}

		/// <summary>
		/// Eject function to call
		/// </summary>
		/// <param name="driveLetter"></param>
		/// <returns></returns>
		public static bool Eject(string driveLetter)
		{
			try
			{
				//driveLetter may be passed as f: or f:\. In the last case the \ must be removed of the driveletter
				if (driveLetter.Contains("\\"))
				{
					driveLetter = driveLetter.Substring(0, driveLetter.IndexOf("\\"));
				}
				IntPtr handle = DoDisMount(driveLetter);
				if (handle != IntPtr.Zero)
				{
					//Step 4: allow for removal the Volume
					int zout = 0;
					if (DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, null, 0, null, 0, out zout, IntPtr.Zero))
					{
						CloseHandle(handle);
						return true;
					}
				}
				//Ensure that no pointer remains open.
				CloseHandle(handle);
				return false;
			}
			catch (Exception)
			{
				return false;
			}
		}

		//Private function that do the actual dismount, returns the handle to the disk. Ensure that the handle is 
		//is closed in the calling functions.
		private static IntPtr DoDisMount(string drive)
		{
			try
			{
				bool ok = false;
				// Step 1: Open Volume
				IntPtr h = CreateFile(@"\\.\" + drive, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
				if (h.ToInt32() == -1) { return IntPtr.Zero; }
				while (true)
				{
					// Step 2: Lock Volume
					for (int i = 0; i < 10; i++)
					{
						//To do the lock we try a couple of times and wait between them
						int nout = 0;
						ok = DeviceIoControl(h, FSCTL_LOCK_VOLUME, null, 0, null, 0, out nout, IntPtr.Zero);
						if (ok)
						{
							break;
						}
						System.Threading.Thread.Sleep(500);
					}
					if (!ok)
					{
						break;
					}
					// Step 3: Dismount Volume
					int xout = 0;
					ok = DeviceIoControl(h, FSCTL_DISMOUNT_VOLUME, null, 0, null, 0, out xout, IntPtr.Zero);
					if (ok)
					{
						return h;
					}
					break;
				}
				//Ensure that there is no pointer open. Close the handle here in case of a failure. 
				CloseHandle(h);
				return IntPtr.Zero;
			}
			catch (Exception)
			{
				return IntPtr.Zero;
			}
		}
	}

	internal class UsbDetector
	{
		//protected override void WndProc(ref Message m)
		//{
		//    switch (m.Msg)
		//    {
		//        case Win32.WM_DEVICECHANGE: OnDeviceChange(ref m); break;
		//    }
		//    base.WndProc(ref m);
		//}

		//void OnDeviceChange(ref Message msg)
		//{
		//    int wParam = (int)msg.WParam;
		//    if (wParam == Win32.DBT_DEVICEARRIVAL) label1.Text = "Arrival";
		//    else if (wParam == Win32.DBT_DEVICEREMOVECOMPLETE) label1.Text =
		//     "Remove";
		//}

		//void RegisterHidNotification()
		//{
		//    Win32.DEV_BROADCAST_DEVICEINTERFACE dbi = new Win32.DEV_BROADCAST_DEVICEINTERFACE();
		//    int size = Marshal.SizeOf(dbi);
		//    dbi.dbcc_size = size;
		//    dbi.dbcc_devicetype = Win32.DBT_DEVTYP_DEVICEINTERFACE;
		//    dbi.dbcc_reserved = 0;
		//    dbi.dbcc_classguid = Win32.GUID_DEVINTERFACE_HID;
		//    dbi.dbcc_name = 0;
		//    IntPtr buffer = Marshal.AllocHGlobal(size);
		//    Marshal.StructureToPtr(dbi, buffer, true);
		//    IntPtr r = Win32.RegisterDeviceNotification(Handle, buffer,
		//    Win32.DEVICE_NOTIFY_WINDOW_HANDLE);
		//    if (r == IntPtr.Zero)
		//        int error = Win32.GetLastError();
		//}

		class Win32
		{
			public const int WM_DEVICECHANGE = 0x0219;
			public const int DBT_DEVICEARRIVAL = 0x8000;
			public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
			public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0;
			public const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
			public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
			public static Guid GUID_DEVINTERFACE_HID = new Guid("4D1E55B2-F16F-11CF-88CB-001111000030");

			[StructLayout(LayoutKind.Sequential)]
			public class DEV_BROADCAST_DEVICEINTERFACE
			{
				public int dbcc_size;
				public int dbcc_devicetype;
				public int dbcc_reserved;
				public Guid dbcc_classguid;
				public short dbcc_name;
			}

			[DllImport("user32.dll", SetLastError = true)]
			public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, Int32 Flags);

			[DllImport("kernel32.dll")]
			public static extern int GetLastError();
		}
	}

	public sealed class UsbHelper : IDisposable
	{
		// CreateFile  - MSDN
		const uint GENERIC_READ = 0x80000000;
		const uint OPEN_EXISTING = 3;
		const uint FILE_SHARE_READ = 0x00000001;
		const uint FILE_SHARE_WRITE = 0x00000002;
		const uint FILE_ATTRIBUTE_NORMAL = 128;
		const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
		static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

		/// <summary>
		/// Events signalized to the client app.
		/// Add handlers for these events in your form to be notified of removable device events 
		/// </summary>
		public event DriveDetectorEventHandler DeviceArrived;
		public event DriveDetectorEventHandler DeviceRemoved;
		public event DriveDetectorEventHandler QueryRemove;

		// HDEVNOTIFY RegisterDeviceNotification(HANDLE hRecipient,LPVOID NotificationFilter,DWORD Flags);
		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		internal static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, uint Flags);

		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		internal static extern uint UnregisterDeviceNotification(IntPtr hHandle);

		// should be "static extern unsafe"
		[DllImport("kernel32", SetLastError = true)]
		internal static extern IntPtr CreateFile(string FileName, // file name
			  uint DesiredAccess,                 // access mode
			  uint ShareMode,                     // share mode
			  uint SecurityAttributes,            // Security Attributes
			  uint CreationDisposition,           // how to create
			  uint FlagsAndAttributes,            // file attributes
			  int hTemplateFile                   // handle to template file
			  );

		[DllImport("kernel32", SetLastError = true)]
		internal static extern bool CloseHandle(IntPtr hObject); // handle to object

		// Structure with information for RegisterDeviceNotification.
		[StructLayout(LayoutKind.Sequential)]
		public struct DEV_BROADCAST_HANDLE
		{
			public int dbch_size;
			public int dbch_devicetype;
			public int dbch_reserved;
			public IntPtr dbch_handle;
			public IntPtr dbch_hdevnotify;
			public Guid dbch_eventguid;
			public long dbch_nameoffset;
			//public byte[] dbch_data[1]; // = new byte[1];
			public byte dbch_data;
			public byte dbch_data1;
		}

		// Struct for parameters of the WM_DEVICECHANGE message
		[StructLayout(LayoutKind.Sequential)]
		public struct DEV_BROADCAST_VOLUME
		{
			public int dbcv_size;
			public int dbcv_devicetype;
			public int dbcv_reserved;
			public int dbcv_unitmask;
		}


		// <summary>
		/// Opens a directory, returns it's handle or zero.
		/// </summary>
		/// <param name="dirPath">path to the directory, e.g. "C:\\dir"</param>
		/// <returns>handle to the directory. Close it with CloseHandle().</returns>
		static public IntPtr OpenDirectory(string dirPath)
		{
			// open the existing file for reading
			IntPtr handle = CreateFile(dirPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
										FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, 0);

			if (handle == INVALID_HANDLE_VALUE)
				return IntPtr.Zero;
			else
				return handle;
		}


		public static bool CloseDirectoryHandle(IntPtr handle)
		{
			return CloseHandle(handle);
		}


		public void Dispose()
		{
			throw new NotImplementedException();
		}
	}

	/// <summary>
	/// Delegate for event handler to handle the device events 
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	public delegate void DriveDetectorEventHandler(Object sender, DriveDetectorEventArgs e);

	/// <summary>
	/// Our class for passing in custom arguments to our event handlers 
	/// 
	/// </summary>
	public class DriveDetectorEventArgs : EventArgs
	{
		public DriveDetectorEventArgs()
		{
			Cancel = false;
			Drive = "";
			HookQueryRemove = false;
		}

		/// <summary>
		/// Get/Set the value indicating that the event should be cancelled 
		/// Only in QueryRemove handler.
		/// </summary>
		public bool Cancel;

		/// <summary>
		/// Drive letter for the device which caused this event 
		/// </summary>
		public string Drive;

		/// <summary>
		/// Set to true in your DeviceArrived event handler if you wish to receive the 
		/// QueryRemove event for this drive. 
		/// </summary>
		public bool HookQueryRemove;
	}
}
